package vip.aning.gitsync.utils;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.PullResult;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.slf4j.LoggerFactory;
import vip.aning.gitsync.constant.AppConst;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Set;
import java.util.stream.Collectors;

public class GitUtils {
    private final org.slf4j.Logger LOG = LoggerFactory.getLogger(this.getClass());
    private final CredentialsProvider credentialsProvider;
    private final String authorName;
    private final String authorEmail;
    private final Object lock = new Object(); // 用于同步的锁对象

    // 构造函数，传入用户名、密码以及提交者别名信息
    public GitUtils(String username, String password, String authorName, String authorEmail) {
        this.credentialsProvider = new UsernamePasswordCredentialsProvider(username, password);
        this.authorName = authorName;
        this.authorEmail = authorEmail;
    }

    /**
     * 克隆仓库
     *
     * @param repoUrl 仓库地址
     * @param localPath 本地路径
     * @throws GitAPIException 如果克隆失败
     */
    public void cloneRepository(String repoUrl, String branchName, String localPath) throws GitAPIException {
        synchronized (lock) {
            CloneCommand cloneCommand = Git.cloneRepository();
            cloneCommand.setURI(repoUrl).setBranch(branchName).setCredentialsProvider(credentialsProvider);
            cloneCommand.setDirectory(new File(localPath));
            try (Git git = cloneCommand.call()) {
                LOG.info("克隆完成: {},,,{}", localPath, git.tag().getName());
            }

        }
    }

    /**
     * 添加文件到 Git 索引
     *
     * @param localPath 本地仓库路径
     * @param filePattern 文件路径或通配符（如 "." 表示所有文件）
     * @throws IOException 如果本地仓库路径无效
     * @throws GitAPIException 如果添加失败
     */
    public void add(String localPath, String filePattern) throws IOException, GitAPIException {
        synchronized (lock) {
            LOG.info("正在添加文件到索引...");
            try (Git git = Git.open(new File(localPath))) {
                git.add()
                        .addFilepattern(filePattern)
                        .call();
                LOG.info("文件添加成功！");
            }
        }
    }

    /**
     * 提交代码
     *
     * @param localPath 本地仓库路径
     * @param message 提交信息
     * @param selectLocalOrRemote
     * @throws IOException 如果本地仓库路径无效
     * @throws GitAPIException 如果提交失败
     */
    public void commit(String localPath, String message, String selectLocalOrRemote) throws IOException, GitAPIException {
        synchronized (lock) {
            LOG.info("正在commit代码...");
            try (Git git = Git.open(new File(localPath))) {
                if (StringUtils.isNotBlank(authorName)
                        && StringUtils.isNotBlank(authorEmail)) {
                    // 设置提交者信息
                    PersonIdent author = new PersonIdent(authorName, authorEmail);
                    PersonIdent committer = new PersonIdent(authorName, authorEmail);

                    git.commit()
                            .setMessage(message)
                            .setAuthor(author) // 设置提交者
                            .setCommitter(committer) // 设置提交者（可选）
                            .call();
                } else {
                    git.commit()
                            .setMessage(message)
                            .call();
                }

                LOG.info("commit成功！");
            }
        }
    }

    /**
     * 拉取最新代码
     *
     * @param localPath 本地仓库路径
     * @throws IOException 如果本地仓库路径无效
     * @throws GitAPIException 如果拉取失败
     */
    public void pull(String localPath) throws IOException, GitAPIException {
        synchronized (lock) {
            LOG.info("正在拉取最新代码...");
            try (Git git = Git.open(new File(localPath))) {
                PullResult result = git.pull()
                        .setCredentialsProvider(credentialsProvider)
                        .call();
                if (result.isSuccessful()) {
                    LOG.info("拉取成功！");
                } else {
                    LOG.info("拉取失败: {}", result);
                }
            }
        }
    }

    /**
     * 拉取远程最新代码并打印变动文件
     *
     * @param localPath 本地仓库路径
     * @throws IOException 如果本地仓库路径无效
     * @throws GitAPIException 如果 Git 操作失败
     */
    public void pullAndPrintChanges(String localPath, String branchName) throws IOException, GitAPIException {
        try (Git git = Git.open(new File(localPath))) {
            Repository repository = git.getRepository();
            String branch = repository.getBranch();
            if (!branch.equals(branchName)) {
                // 切换到目标分支
                LOG.info("切换到分支: {}", branchName);
                git.checkout()
                        .setName(branchName)
                        .call();
            }

            // 拉取远程最新代码
            LOG.info("正在拉取远程最新代码...");
            PullResult pullResult = git.pull()
                    .setCredentialsProvider(credentialsProvider)
                    .call();

            if (pullResult.isSuccessful()) {
                LOG.info("拉取成功！");
            } else {
                LOG.info("拉取失败：{}", pullResult);
                return;
            }

            // 获取变动文件
            LOG.info("正在获取变动文件...");
            Status status = git.status().call();

            // 打印新增文件
            Set<String> addedFiles = status.getAdded();
            if (!addedFiles.isEmpty()) {
                LOG.info("新增文件:");
                addedFiles.forEach(LOG::info);
            }

            // 打印修改文件
            Set<String> modifiedFiles = status.getModified();
            if (!modifiedFiles.isEmpty()) {
                LOG.info("修改文件:");
                modifiedFiles.forEach(LOG::info);
            }

            // 打印删除文件
            Set<String> removedFiles = status.getRemoved();
            if (!removedFiles.isEmpty()) {
                LOG.info("删除文件:");
                removedFiles.forEach(LOG::info);
            }

            // 打印未跟踪文件
            Set<String> untrackedFiles = status.getUntracked();
            if (!untrackedFiles.isEmpty()) {
                LOG.info("未跟踪文件:");
                untrackedFiles.forEach(LOG::info);
            }

            // 打印冲突文件
            Set<String> conflictingFiles = status.getConflicting();
            if (!conflictingFiles.isEmpty()) {
                LOG.info("冲突文件:");
                conflictingFiles.forEach(LOG::info);
            }
            LOG.info("拉取任务完成！");
        }
    }

    /**
     * 推送代码到远程仓库
     *
     * @param localPath 本地仓库路径
     * @throws IOException 如果本地仓库路径无效
     */
    public void push(String localPath) throws IOException {
        synchronized (lock) {
            LOG.info("正在推送代码...");
            int maxRetries = 3; // 最大重试次数
            int attempt = 0;    // 当前尝试次数

            while (attempt < maxRetries) {
                try (Git git = Git.open(new File(localPath))) {
                    LOG.info("正在尝试 Push（第 {} 次）...", (attempt + 1));
                    git.push()
                            .setCredentialsProvider(credentialsProvider)
                            .call();
                    LOG.info("Push 成功！");
                    return; // 如果 Push 成功，直接返回
                } catch (GitAPIException e) {
                    attempt++;
                    System.err.println("Push 失败：" + e.getMessage());
                    if (attempt >= maxRetries) {
                        LOG.info("Push 操作失败超过最大重试次数 ({})，请检查网络或远程仓库配置。", maxRetries, e);
                    }
                    LOG.info("等待 3 秒后重试...");
                }

                try {
                    Thread.sleep(3000); // 等待 3 秒后重试
                } catch (InterruptedException e) {
                    LOG.error(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * 提交本地变动并推送到远程仓库
     *
     * @param localPath 本地仓库路径
     * @param commitMessage 提交信息
     * @throws IOException 如果本地仓库路径无效
     * @throws GitAPIException 如果提交或推送失败
     */
    public void commitAndPush(String localPath, String commitMessage, String selectLocalOrRemote) throws IOException, GitAPIException {
        add(localPath, ".");
        commit(localPath, commitMessage, selectLocalOrRemote);
        push(localPath);
    }

    /**
     * 检查本地仓库是否有文件变动
     *
     * @param localPath 本地仓库路径
     * @return true 如果有变动，否则 false
     *
     * @throws IOException 如果本地仓库路径无效
     * @throws GitAPIException 如果检测失败
     */
    public boolean hasLocalChanges(String localPath) throws IOException, GitAPIException {
        synchronized (lock) {
            try (Git git = Git.open(new File(localPath))) {
                Status status = git.status().call();
                return !status.isClean(); // 如果仓库不是干净的，说明有变动
            }
        }
    }

    /**
     * 获取本地仓库的变动文件
     *
     * @param localPath 本地仓库路径
     * @throws IOException 如果本地仓库路径无效
     * @throws GitAPIException 如果获取状态失败
     */
    public void printChangedFiles(String localPath) throws IOException, GitAPIException {
        synchronized (lock) {
            try (Git git = Git.open(new File(localPath))) {
                // 获取仓库状态
                Status status = git.status().call();

                // 获取新增文件
                Set<String> addedFiles = status.getAdded();
                LOG.info("新增文件:");
                addedFiles.forEach(LOG::info);

                // 获取修改文件
                Set<String> modifiedFiles = status.getModified();
                LOG.info("修改文件:");
                modifiedFiles.forEach(LOG::info);

                // 获取删除文件
                Set<String> removedFiles = status.getRemoved();
                LOG.info("删除文件:");
                removedFiles.forEach(LOG::info);

                // 获取未跟踪文件
                Set<String> untrackedFiles = status.getUntracked();
                LOG.info("未跟踪文件:");
                untrackedFiles.forEach(LOG::info);

                // 获取被忽略的文件
                Set<String> ignoredFiles = status.getIgnoredNotInIndex();
                LOG.info("被忽略的文件:");
                ignoredFiles.forEach(LOG::info);

                // 获取冲突文件
                Set<String> conflictingFiles = status.getConflicting();
                LOG.info("冲突文件:");
                conflictingFiles.forEach(LOG::info);
            }
        }
    }

    /**
     * 以本地文件为准，解决冲突并覆盖远程
     *
     * @param localPath 本地仓库路径
     * @param commitMessage 提交信息
     * @throws IOException 如果本地仓库路径无效
     * @throws GitAPIException 如果 Git 操作失败
     */
    public void resolveConflictWithLocal(String localPath, String commitMessage) throws IOException, GitAPIException {
        synchronized (lock) {
            try (Git git = Git.open(new File(localPath))) {
                // 获取冲突文件
                Set<String> conflictingFiles = git.status().call().getConflicting();
                if (conflictingFiles.isEmpty()) {
                    LOG.info("没有冲突文件，无需处理。");
                    return;
                }

                LOG.info("检测到冲突文件，以下文件将以本地为准：");
                conflictingFiles.forEach(LOG::info);

                // 将冲突文件标记为已解决
                for (String file : conflictingFiles) {
                    git.add().addFilepattern(file).call();
                }

                // 提交本地更改
                git.commit().setMessage(commitMessage).call();
                LOG.info("冲突已解决并提交。");

                // 强制推送到远程仓库
                git.push()
                        .setCredentialsProvider(credentialsProvider)
                        .setForce(true) // 强制推送
                        .call();
                LOG.info("本地更改已覆盖远程仓库。");
            }
        }
    }

    /**
     * 以远程文件为准，解决冲突并覆盖本地
     *
     * @param localPath 本地仓库路径
     * @throws IOException 如果本地仓库路径无效
     * @throws GitAPIException 如果 Git 操作失败
     */
    public void resolveConflictWithRemote(String localPath) throws IOException, GitAPIException {
        synchronized (lock) {
            try (Git git = Git.open(new File(localPath))) {
                LOG.info("检测到冲突，正在重置本地仓库到远程最新状态...");

                // 重置本地仓库到远程最新状态
                git.reset()
                        .setMode(org.eclipse.jgit.api.ResetCommand.ResetType.HARD)
                        .call();

                // 拉取远程代码覆盖本地
                git.pull()
                        .setCredentialsProvider(credentialsProvider)
                        .call();
                LOG.info("本地仓库已重置并覆盖为远程代码。");
            }
        }
    }

    /**
     * 检查本地路径是否是有效的 Git 仓库
     *
     * @param localPath 本地仓库路径
     * @return true 如果是有效的 Git 仓库，否则 false
     */
    public boolean isValidRepository(String localPath) {
        synchronized (lock) {
            try (Git git = Git.open(new File(localPath))) {
                Repository repository = git.getRepository();
                return repository.getDirectory().exists();
            } catch (IOException e) {
                LOG.error("无效的 Git 仓库: {}", e.getMessage());
                return false;
            }
        }
    }

    /**
     * 检查用户凭据是否正确
     *
     * @param repoUrl 仓库地址
     * @return true 如果用户凭据正确，否则 false
     */
    public boolean isValidUser(String repoUrl) {
        synchronized (lock) {
            try {
                Git.lsRemoteRepository()
                        .setCredentialsProvider(credentialsProvider)
                        .setRemote(repoUrl)
                        .call();
                return true;
            } catch (Exception e) {
                LOG.error("用户凭据验证失败: {}", e.getMessage());
                return false;
            }
        }
    }

    /**
     * 检测远程仓库是否存在指定分支
     *
     * @param remoteUrl 远程仓库地址
     * @param branchName 要检测的分支名称
     * @return true 如果分支存在，否则 false
     *
     * @throws GitAPIException 如果 Git 操作失败
     */
    public boolean isRemoteBranchExists(String remoteUrl, String branchName) throws GitAPIException {
        synchronized (lock) {
            // 列出远程仓库的所有引用
            Collection<Ref> refs = Git.lsRemoteRepository()
                    .setCredentialsProvider(credentialsProvider)
                    .setRemote(remoteUrl)
                    .call();

            // 检查是否存在指定的分支
            String branchRef = "refs/heads/" + branchName;
            return refs.stream()
                    .map(Ref::getName)
                    .peek(LOG::info)
                    .collect(Collectors.toSet())
                    .contains(branchRef);
        }
    }

}